﻿using System;
using System.Collections.Generic;
using System.Text;
using MathNet.Numerics.LinearAlgebra;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;


namespace ImgProcessGui
{
    
    class Algorithems
    {

        
        /// <summary>
        /// Create & save histogram under :"@C:\Program Files\IdentifierFiles\Pic2Classify\Dir"
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="fileName"></param>
        /// <param name="dictionary"></param>
        /// <param name="jump_Size"></param>
        /// <returns></returns>
        public Vector createHistogram(String dir, String fileName, Matrix dictionary, int jump_Size)
        {
            MatrixFunctions matFunc = new MatrixFunctions();
            double score;
            ImageFunctions ImgFnc = new ImageFunctions();
            //dictionary lenght is numbers of columns
            Vector hist = Vector.Zeros(dictionary.ColumnCount);
            double totalCount = 0.0;
            Bitmap m = new Bitmap(fileName);
            
            byte[] PicDat;
            Matrix RGB_PatchAvg_Mat;
            Matrix GreyScale_Mat;
            int patchSize;
            PicDat = ImgFnc.BitMap2Bytes24RGB(ref m, GuiParameters.patch_H, jump_Size,out GreyScale_Mat,out RGB_PatchAvg_Mat);
            // patches number is the rows number each patch is in a row
            patchSize = GuiParameters.patch_H * GuiParameters.patch_H * 3;// Changed  - add "*3"
        
            //int rowNum = PicDat.Length / (patchSize);
            //// converting data
            //double[][] picAsDouble = new double[rowNum][];
            //for (int row = 0; row < rowNum; row++)
            //{
            //    picAsDouble[row] = new double[patchSize];
            //    for (int col = 0; col < patchSize; col++)
            //    {
            //        picAsDouble[row][col] = Convert.ToDouble(PicDat[row * patchSize + col]);
            //    }
            //}

            Matrix DicEigVecs;
            matFunc.LoadMatrix("Dictionary", "EigenVectors", out DicEigVecs);
            
            //Matrix datMatrix = new Matrix(picAsDouble);
            
            //Noremlize works with pathces in columns
            //datMatrix.Transpose();
            NoremlizeMatrixRows(ref GreyScale_Mat);
            GreyScale_Mat.Transpose();
            //Matrix eigVectorsMat;
            //Matrix pcaMat = preformPca(GuiParameters.data_Depth, datMatrix, out eigVectorsMat);
            //pcaMat.Transpose();
            Matrix pcaMat = GreyScale_Mat * DicEigVecs;
            RGB_PatchAvg_Mat.Transpose();
            RGB_PatchAvg_Mat.Multiply(GuiParameters.rGBFactor);
            pcaMat = MatrixFunctions.GlueMatrixSideBySide(pcaMat, RGB_PatchAvg_Mat);
            dictionary.Transpose();
            int[] PicPix2Dic = Apply2Centers_Norm1(pcaMat.GetArray(), dictionary.GetArray(), out score);
            dictionary.Transpose();
            //histogram
            for (int i = 0; i < PicPix2Dic.Length; i++)
            {
                hist[PicPix2Dic[i]]++;
                totalCount++;
            }
            //noremlize histogram
            hist.ScaleInplace(1.0 / totalCount);
            //=====================Saving Histograms=============
            Matrix HistMat = new Matrix(1, hist.Length);
            HistMat.SetRowVector(hist, 0);
            matFunc.SaveMatrix(dir, fileName.Remove(0, dir.Length + 34), HistMat);
            matFunc.PrintMatToFile(HistMat.GetArray(), fileName.Remove(0, dir.Length + 34));
            //===================================================
            m.Dispose();
            return hist;
        }

     

        //Works only for jumps of 1 or jumps of 5 (patch size 10),max 15 centers
        public Vector createCenteredBmp(string dir2Load, Matrix dictionary, string file2Save, int jump_Size)
        {
            double score;
            MatrixFunctions matFunc = new MatrixFunctions();
            ImageFunctions ImgFnc = new ImageFunctions();
            //dictionary lenght is numbers of columns
            Vector hist = Vector.Zeros(dictionary.ColumnCount);
            
            if (Directory.Exists(@".\" + dir2Load))
            {
                String[] files = Directory.GetFiles(dir2Load, "*.bmp");
                int[][] Pix2Dic = new int[files.Length][];
                foreach (String fileName in files)
                {
                    Bitmap m = new Bitmap(fileName);
                    byte[] PicDat = ImgFnc.BitMap2Bytes(ref m, GuiParameters.patch_H, jump_Size);
                    // patches number is the rows number each patch is in a row
                    int patchSize = GuiParameters.patch_H * GuiParameters.patch_H;
                    int rowNum = PicDat.Length / (patchSize);

                    double[][] picAsDouble = new double[rowNum][];
                    for (int row = 0; row < rowNum; row++)
                    {
                        picAsDouble[row] = new double[patchSize];
                        for (int col = 0; col < patchSize; col++)
                        {
                            picAsDouble[row][col] = Convert.ToDouble(PicDat[row * patchSize + col]);
                        }

                    }
                    Matrix datMatrix = new Matrix(picAsDouble);
                    datMatrix.Transpose();
                    NoremlizeMatrixRows(ref datMatrix);
                    datMatrix.Transpose();

                    dictionary.Transpose();
                    int[] PicPix2Dic = Apply2Centers_Norm1(datMatrix.GetArray(), dictionary.GetArray(), out score);
                    dictionary.Transpose();


                    byte[] bmpBytes = new byte[PicPix2Dic.Length];//jump_Size*jump_Size - TODO - Change to something logic
                
                    for (int c = 0; c < PicPix2Dic.Length; c++)
                    {

                        bmpBytes[c] = (byte)(PicPix2Dic[c] * (255 / dictionary.ColumnCount));
                        
                    }

                    Bitmap img = ImgFnc.SequenceByteArray2Bitmap(bmpBytes, m.Width - (GuiParameters.patch_H), m.Height - (GuiParameters.patch_H));
                    //Bitmap img = ImgFnc.Bytes2Bitmap(bmpBytes, 10, m.Width, m.Height);
                    img.Save("Pixeled.bmp");
                    //------------------------------------------------------------

                }
         
            }
            //=====================Saving Histograms=============
            Matrix HistMat = new Matrix(1, hist.Length);
            HistMat.SetRowVector(hist, 0);
            matFunc.SaveMatrix(dir2Load, file2Save, HistMat);
            matFunc.PrintMatToFile(HistMat.GetArray(), file2Save);
            //===================================================

            return hist;
        }
        public int[] Apply2Centers_Norm1(double[][] Matrix, double[][] Center,out double score)
        {
            int[] indexCenter = new int[Matrix.GetLength(0)];
            Norm Norm2 = new Norm();
            double minNorm ;
            score = 0;
            for (int i = 0; i < Matrix.GetLength(0); i++)
            {
                minNorm = double.MaxValue;
                for (int j = 0; j < Center.GetLength(0); j++)
                {
                    if (Norm2.Norm2(Matrix[i], Center[j]) < minNorm)
                    {
                        minNorm = Norm2.Norm2(Matrix[i], Center[j]);// TODO - Add Value
                        indexCenter[i] = j;
                    }
                }
                //Calculating Score
                score = score + minNorm;
            }
            return indexCenter;
        }

        public Matrix FindNewCenterByAvg(double[][] MyMatrix, int[] CurrentCenterIndex)
            {          
            MyVectorFunctions vec = new MyVectorFunctions();       
            //TODO - Check that Matrix and current center index are same sized
            int numOfCenters = 1;
            int[] indexCenter = (int[])CurrentCenterIndex.Clone();
            System.Array.Sort(indexCenter);
            for (int i = 0; i < indexCenter.Length - 1; i++)
            {
                if (indexCenter[i] != indexCenter[i + 1])
                {
                    numOfCenters++;
                }
            }
            Matrix newCenter = new Matrix(numOfCenters, MyMatrix[0].GetLength(0)); //Here we will fill our new centers
            Matrix avg = new Matrix(FindMaxIntArray(CurrentCenterIndex)+1, MyMatrix[0].GetLength(0));
            int[] isFilledVector = new int[MyMatrix.GetLength(0)];
            isFilledVector.Initialize();
            //find match for all indexes
            for (int i = 0; i < MyMatrix.GetLength(0); i++)
            {
           //     vec.AddVectors(out (avg.GetArray())[CurrentCenterIndex[i]], (avg.GetArray())[CurrentCenterIndex[i]], MyMatrix[i]);
                for (int pntInCnt = 0; pntInCnt < (avg.GetArray())[CurrentCenterIndex[i]].Length; pntInCnt++)
                {
                    (avg.GetArray())[CurrentCenterIndex[i]][pntInCnt] += MyMatrix[i][pntInCnt];
                }
                isFilledVector[CurrentCenterIndex[i]]++;
            }
            int j=0;
            for (int i = 0; i < (FindMaxIntArray(CurrentCenterIndex) + 1); i++)
            {
                if(isFilledVector[i]>0)
                {
                    //vec.AvgVector(out newCenter.GetArray()[j], (avg.GetArray())[i],Convert.ToDouble( isFilledVector[i]));
                   
                    for(int pntInCnt=0;pntInCnt < (avg.GetArray())[i].Length;pntInCnt++)
                    {
                        (newCenter.GetArray())[j][pntInCnt] = (avg.GetArray())[i][pntInCnt] / ((double)isFilledVector[i]);
                    }
                    j++;
                }
            }
            
            return newCenter;
        }

        //TODO - check if there is need to add: out score
        public Matrix K_Mean(Matrix Points, Matrix Centers, int Num_Of_Itterations)
        {
            MatrixFunctions matFunc = new MatrixFunctions();
            Points.Transpose();
            Centers.Transpose();
            int[] matCenterIndexes;
            double score;
            double oldScore = double.MaxValue;
            matFunc.PrintMatToFile(Points.GetArray(),"points" + ".csv");
            matFunc.PrintMatToFile(Centers.GetArray(), "origcenters" + ".csv");
            for (int i = 0; i < Num_Of_Itterations; i++)
            {
                matCenterIndexes = Apply2Centers_Norm1(Points.GetArray(), Centers.GetArray(), out score);//Core algorithem part1
                //=================================
                //double[][] indexMat = new double[1][];
                //indexMat[0] = new double[matCenterIndexes.Length];
                //for (int j = 0; j < matCenterIndexes.Length; j++)
                //    indexMat[0][j] = Convert.ToDouble(matCenterIndexes[j]);
                //Matrix indexMatrix = new Matrix(indexMat);
                //indexMatrix.Transpose();
                //matFunc.PrintMatToFile(indexMatrix, "Index" + i.ToString() + ".csv");
                //=================================

                Centers = FindNewCenterByAvg(Points.GetArray(), matCenterIndexes);//Core algorithem part2                                    
                matFunc.PrintMatToFile(Centers.GetArray(),"centers" + i.ToString()+".csv");
                if (((oldScore - score) / oldScore) < 0.001)//((oldScore - score) / oldScore) < 0.001
                {
                    if((oldScore-score)< 0 )
                    {
                        MessageBox.Show("Negitive Score");
                    }
                    break;
                }
                oldScore = score;
            }
            Points.Transpose();
            Centers.Transpose();
            return Centers;
        }
        
        public int FindMaxIntArray(int[] Arr)
        {
            int max = Arr[0];
            for (int i = 1; i < Arr.Length;i++ )
            {
                if(Arr[i]>max)
                {
                    max = Arr[i];
                }
            }

            return max;
        }
        public int FindMinIntArray(int[] Arr)
        {
            int min = Arr[0];
            for (int i = 1; i < Arr.Length; i++)
            {
                if (Arr[i] < min)
                {
                    min = Arr[i];
                }
            }

            return min;
        }
        public double FindMaxIntArray(double[] Arr)
        {
            double max = Arr[0];
            for (int i = 1; i < Arr.Length; i++)
            {
                if (Arr[i] > max)
                {
                    max = Arr[i];
                }
            }

            return max;
        }
        public double FindMinIntArray(double[] Arr)
        {
            double min = Arr[0];
            for (int i = 1; i < Arr.Length; i++)
            {
                if (Arr[i] < min)
                {
                    min = Arr[i];
                }
            }

            return min;
        }
        public int FindMinIndexDoubleArray(double[] Arr)
        {
            double min = Arr[0];
            int minIndex=0;
            for (int i = 1; i < Arr.Length; i++)
            {
                if (Arr[i] < min)
                {
                    min = Arr[i];
                    minIndex = i;
                }
            }

            return minIndex;
        }

        //******************************************************************************************
        //preformPca:the function preform a rank reduction algorithm pca
        //parameters:newDim = the new dimension to be reduced to
        // matToLower - a matrix containing data to be reduced each measurement is a row in the matrix
        // EigVecsMat - a matrix containing the eigen vector chosaen in the pca an out parameter
        // return value: new reduced Matrix
        //******************************************************************************************
        public static Matrix preformPca(int newDim, Matrix matToLower,out Matrix EigVecsMat)
        {

            Matrix CovMat = getCovMatrix(matToLower);
            int eigVecLength = CovMat.EigenVectors.RowCount;
            int[] pos = new int[newDim];
            Matrix eigenMat = CovMat.SingularValueDecomposition.RightSingularVectors.GetMatrix(0, eigVecLength - 1, 0, newDim-1);
          if (GuiParameters.toScale)
          {
              //noremlize the eigen vectors by the sqrt of their eigen values
              for (int col = 0; col < eigenMat.ColumnCount; col++)
              {
                  double scaleFactor = 1 / Math.Sqrt(CovMat.SingularValueDecomposition.SingularValues[col]);
                  eigenMat.SetColumnVector(eigenMat.GetColumnVector(col).Scale(scaleFactor), col);
              }
          }

            EigVecsMat = eigenMat.Clone();
            eigenMat.Transpose();
            return eigenMat * matToLower;



        }
        //******************************************************************************************
        //getCovMatrix:the function returns the covariance Matrix of the given Matrix
        //parameters:orgMat - the original Matrix 
        //return value: Covariance matrix
        //******************************************************************************************
        public static Matrix getCovMatrix(Matrix originalMat)
        {
            Matrix orgMat = originalMat.Clone();
            orgMat.Transpose();
            Matrix resMat = new Matrix(orgMat.RowCount, orgMat.ColumnCount);
            Vector meanVec = Vector.Zeros(orgMat.ColumnCount);
            // calculating mean vector
            for (int row = 0; row < orgMat.RowCount; row++)
            {
                meanVec.AddInplace(orgMat.GetRowVector(row));
            }
            double scalar = 1.0 / orgMat.RowCount;
            meanVec.ScaleInplace(scalar);
            // removing mean from elements
            for (int row = 0; row < orgMat.RowCount; row++)
            {
                Vector vec = orgMat.GetRowVector(row);
                vec.SubtractInplace(meanVec);
                orgMat.SetRowVector(vec, row);
            }
            //calculating
            Matrix trnsMat = orgMat.Clone();
            trnsMat.Transpose();
            scalar = 1.0 / (orgMat.RowCount - 1);
            orgMat.Multiply(scalar);
            resMat = trnsMat.Multiply(orgMat);
            return resMat;

        }

        //******************************************************************************************
        //processData:the function preform a rank reduction algorithm pca
        //parameters :
        // pictureData - an array containing the tiles data
        // wordLength - the length of a word(length*width of the tile)
        // DataLevel - the data level to reduce a word to 
        // centersNum - numbers of centers to work with
        // KmeanItNum - how many iterations in the K mean algorithm
        // isNormalize - should you normalize the data to preform algorithm on 
        // finalCenters - the final centers before reconstruction
        //return value: new reduced Matrix
        //******************************************************************************************
        public void processData(byte[] pictureData, int wordLength, int DataLevel, int centersNum, int KmeanItNum, bool isNormalize, Matrix iniCenters, out Matrix finalCenters, out  Matrix pcaEigenVectors, Matrix data_GreyScale, Matrix data_RGB_PatchAvg)
        {
            // Centers
            GC.Collect(); //TODO -remove?
            Algorithems ImageProceesAlgorithems = new Algorithems();
            
            // the transposed data of the eigen vectors from the pcs
            Matrix EigenVectors = new Matrix(DataLevel, wordLength);
                      
            #region noremalize   
            if (isNormalize)
            {
                NoremlizeMatrixRows(ref data_GreyScale);
            }
            #endregion
            Matrix pcaMatrix = preformPca(DataLevel, data_GreyScale, out EigenVectors);
            pcaEigenVectors = EigenVectors;
            
            //====Normalizng data_RGB_PatchAvg===============================
            Matrix data_RGB_PatchAvg_Normalized =  data_RGB_PatchAvg.Clone();
            data_RGB_PatchAvg_Normalized.Multiply(GuiParameters.rGBFactor);
            //===============================================================

            Matrix FullData_Mat = MatrixFunctions.GlueMatrixUpToDown(pcaMatrix, data_RGB_PatchAvg_Normalized); 
            finalCenters = ImageProceesAlgorithems.K_Mean(FullData_Mat, iniCenters, KmeanItNum);
           
        }
         //******************************************************************************************
        //NoremlizeMatrixRows:the function noremliazes the matrix over he matrix rows
        // substructing the average row from all the row
        //parameters :
        //DataMatrix - matrix to noremlize
        //******************************************************************************************
      
        private static void NoremlizeMatrixRows(ref Matrix DataMatrix)
        {
            Vector normelizeVec = Vector.Zeros(DataMatrix.ColumnCount);
            for (int row = 0; row < DataMatrix.RowCount; row++)
            {
                normelizeVec.AddInplace(DataMatrix.GetRowVector(row));
            }
            normelizeVec.ScaleInplace(1.0 / DataMatrix.RowCount);
            for (int row = 0; row < DataMatrix.RowCount; row++)
            {
                DataMatrix.SetRowVector(DataMatrix.GetRowVector(row)-normelizeVec,row);
            }
        }

    }
}
